// NTService.cpp
//
// Implementation of BasicNTService

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "BasicNTService.h"

namespace utility
{
	// static variables
	BasicNTService* BasicNTService::m_pThis = NULL;

	BasicNTService::BasicNTService(const TCHAR* szServiceName)
	{
		// copy the address of the current object so we can access it from
		// the static member callback functions. 
		// WARNING: This limits the application to only one BasicNTService object. 
		m_pThis = this;

		// Set the default service name and version
		ZeroMemory(m_szServiceName, sizeof(m_szServiceName));
		_tcsncpy(m_szServiceName, szServiceName, sizeof(m_szServiceName)/sizeof(TCHAR) -1);//strncpy(m_szServiceName, szServiceName, sizeof(m_szServiceName)-1);
		m_iMajorVersion = 1;
		m_iMinorVersion = 0;
		m_hEventSource = NULL;

		// set up the initial service status 
		m_hServiceStatus = NULL;
		m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
		m_Status.dwCurrentState = SERVICE_STOPPED;
		m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
		m_Status.dwWin32ExitCode = 0;
		m_Status.dwServiceSpecificExitCode = 0;
		m_Status.dwCheckPoint = 0;
		m_Status.dwWaitHint = 0;
		m_bIsRunning = FALSE;
	}

	BasicNTService::~BasicNTService()
	{
		DebugMsg(_T("BasicNTService::~BasicNTService()"));
		if (m_hEventSource) {
			::DeregisterEventSource(m_hEventSource);
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////
	// Default command line argument parsing

	// Returns TRUE if it found an arg it recognised, FALSE if not
	// Note: processing some arguments causes output to stdout to be generated.
	BOOL BasicNTService::ParseStandardArgs(int argc, TCHAR* argv[])
	{
		// See if we have any command line args we recognise
		if (argc <= 1) return FALSE;

		if (_tcsicmp(argv[1], _T("-v")) == 0) {//if (_stricmp(argv[1], "-v") == 0) {

			// Spit out version info
			_tprintf(_T("%s Version %d.%d\n"),
				m_szServiceName, m_iMajorVersion, m_iMinorVersion);
			_tprintf(_T("The service is %s installed\n"),
				IsInstalled() ? _T("currently") : _T("not"));
			return TRUE; // say we processed the argument

		} else if (_tcsicmp(argv[1], _T("-i")) == 0) {//} else if (_stricmp(argv[1], "-i") == 0) {

			// Request to install.
			if (IsInstalled()) {
				_tprintf(_T("%s is already installed\n"), m_szServiceName);//printf("%s is already installed\n", m_szServiceName);
			} else {
				// Try and install the copy that's running
				if (Install()) {
					_tprintf(_T("%s installed\n"), m_szServiceName);
				} else {
					_tprintf(_T("%s failed to install. Error %d\n"), m_szServiceName, GetLastError());
				}
			}
			return TRUE; // say we processed the argument

		} else if (_tcsicmp(argv[1], _T("-u")) == 0) {//} else if (_stricmp(argv[1], "-u") == 0) {

			// Request to uninstall.
			if (!IsInstalled()) {
				_tprintf(_T("%s is not installed\n"), m_szServiceName);
			} else {
				// Try and remove the copy that's installed
				if (Uninstall()) {
					// Get the executable file path
					TCHAR szFilePath[_MAX_PATH];
					::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath) / sizeof(TCHAR));
					_tprintf(_T("%s removed. (You must delete the file (%s) yourself.)\n"),
						m_szServiceName, szFilePath);
				} else {
					_tprintf(_T("Could not remove %s. Error %d\n"), m_szServiceName, GetLastError());
				}
			}
			return TRUE; // say we processed the argument

		}

		// Don't recognise the args
		return FALSE;
	}

	////////////////////////////////////////////////////////////////////////////////////////
	// Install/uninstall routines

	// Test if the service is currently installed
	BOOL BasicNTService::IsInstalled()
	{
		BOOL bResult = FALSE;

		// Open the Service Control Manager
		SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
			NULL, // ServicesActive database
			SC_MANAGER_ALL_ACCESS); // full access
		if (hSCM) {

			// Try to open the service
			SC_HANDLE hService = ::OpenService(hSCM,
				m_szServiceName,
				SERVICE_QUERY_CONFIG);
			if (hService) {
				bResult = TRUE;
				::CloseServiceHandle(hService);
			}

			::CloseServiceHandle(hSCM);
		}

		return bResult;
	}

	BOOL BasicNTService::Install()
	{
		// Open the Service Control Manager
		SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
			NULL, // ServicesActive database
			SC_MANAGER_ALL_ACCESS); // full access
		if (!hSCM) return FALSE;

		// Get the executable file path
		TCHAR szFilePath[_MAX_PATH] = {0};
		::GetModuleFileName(NULL, szFilePath, _MAX_PATH/*sizeof(szFilePath)*/);

		// Create the service
		SC_HANDLE hService = ::CreateService(hSCM,
			m_szServiceName,
			m_szServiceName,
			SERVICE_ALL_ACCESS,
			SERVICE_WIN32_OWN_PROCESS,
			SERVICE_DEMAND_START,        // start condition
			SERVICE_ERROR_NORMAL,
			szFilePath,
			NULL,
			NULL,
			NULL,
			NULL,
			NULL);
		if (!hService) {
			::CloseServiceHandle(hSCM);
			return FALSE;
		}

		// make registry entries to support logging messages
		// Add the source name as a subkey under the Application
		// key in the EventLog service portion of the registry.
		TCHAR szKey[512] = {0};
		HKEY hKey = NULL;
		_tcscpy(szKey, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"));
		_tcscat(szKey, m_szServiceName);
		if (::RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS) {
			::CloseServiceHandle(hService);
			::CloseServiceHandle(hSCM);
			return FALSE;
		}

		// Add the Event ID message-file name to the 'EventMessageFile' subkey.
		::RegSetValueEx(hKey,
			_T("EventMessageFile"),
			0,
			REG_EXPAND_SZ, 
			(CONST BYTE*)szFilePath,
			sizeof(szFilePath) + 1);     

		// Set the supported types flags.
		DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
		::RegSetValueEx(hKey,
			_T("TypesSupported"),
			0,
			REG_DWORD,
			(CONST BYTE*)&dwData,
			sizeof(DWORD));
		::RegCloseKey(hKey);

		LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);

		// tidy up
		::CloseServiceHandle(hService);
		::CloseServiceHandle(hSCM);
		return TRUE;
	}

	BOOL BasicNTService::Uninstall()
	{
		// Open the Service Control Manager
		SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
			NULL, // ServicesActive database
			SC_MANAGER_ALL_ACCESS); // full access
		if (!hSCM) return FALSE;

		BOOL bResult = FALSE;
		SC_HANDLE hService = ::OpenService(hSCM,
			m_szServiceName,
			DELETE);
		if (hService) {
			if (::DeleteService(hService)) {
				LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, m_szServiceName);
				bResult = TRUE;
			} else {
				LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, m_szServiceName);
			}
			::CloseServiceHandle(hService);
		}

		::CloseServiceHandle(hSCM);
		return bResult;
	}

	///////////////////////////////////////////////////////////////////////////////////////
	// Logging functions

	// This function makes an entry into the application event log
	void BasicNTService::LogEvent(WORD wType, DWORD dwID,
		const TCHAR* pszS1,
		const TCHAR* pszS2,
		const TCHAR* pszS3)
	{
		const TCHAR* ps[3];
		ps[0] = pszS1;
		ps[1] = pszS2;
		ps[2] = pszS3;

		int iStr = 0;
		for (int i = 0; i < 3; i++) {
			if (ps[i] != NULL) iStr++;
		}

		// Check the event source has been registered and if
		// not then register it now
		if (!m_hEventSource) {
			m_hEventSource = ::RegisterEventSource(NULL,  // local machine
				m_szServiceName); // source name
		}

		if (m_hEventSource) {
			::ReportEvent(m_hEventSource,
				wType,
				0,
				dwID,
				NULL, // sid
				iStr,
				0,
				ps,
				NULL);
		}
	}

	//////////////////////////////////////////////////////////////////////////////////////////////
	// Service startup and registration

	BOOL BasicNTService::StartService()
	{
		SERVICE_TABLE_ENTRY st[] = {
			{m_szServiceName, ServiceMain},
			{NULL, NULL}
		};

		DebugMsg(_T("Calling StartServiceCtrlDispatcher()"));
		BOOL b = ::StartServiceCtrlDispatcher(st);
		DebugMsg(_T("Returned from StartServiceCtrlDispatcher()"));
		return b;
	}

	// static member function (callback)
	void BasicNTService::ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
	{
		// Get a pointer to the C++ object
		BasicNTService* pService = m_pThis;

		pService->DebugMsg(_T("Entering BasicNTService::ServiceMain()"));
		// Register the control request handler
		pService->m_Status.dwCurrentState = SERVICE_START_PENDING;
		pService->m_hServiceStatus = RegisterServiceCtrlHandler(pService->m_szServiceName,
			Handler);
		if (pService->m_hServiceStatus == NULL) {
			pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_CTRLHANDLERNOTINSTALLED);
			return;
		}

		// Start the initialisation
		if (pService->Initialize()) {

			// Do the real work. 
			// When the Run function returns, the service has stopped.
			pService->m_bIsRunning = TRUE;
			pService->m_Status.dwWin32ExitCode = 0;
			pService->m_Status.dwCheckPoint = 0;
			pService->m_Status.dwWaitHint = 0;
			pService->Run();
		}

		// Tell the service manager we are stopped
		pService->SetStatus(SERVICE_STOPPED);

		pService->DebugMsg(_T("Leaving BasicNTService::ServiceMain()"));
	}

	///////////////////////////////////////////////////////////////////////////////////////////
	// status functions

	void BasicNTService::SetStatus(DWORD dwState)
	{
		DebugMsg(_T("BasicNTService::SetStatus(%lu, %lu)"), m_hServiceStatus, dwState);
		m_Status.dwCurrentState = dwState;
		::SetServiceStatus(m_hServiceStatus, &m_Status);
	}

	///////////////////////////////////////////////////////////////////////////////////////////
	// Service initialization

	BOOL BasicNTService::Initialize()
	{
		DebugMsg(_T("Entering BasicNTService::Initialize()"));

		// Start the initialization
		SetStatus(SERVICE_START_PENDING);

		// Perform the actual initialization
		BOOL bResult = OnInit(); 

		// Set final state
		m_Status.dwWin32ExitCode = GetLastError();
		m_Status.dwCheckPoint = 0;
		m_Status.dwWaitHint = 0;
		if (!bResult) {
			LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_FAILEDINIT);
			SetStatus(SERVICE_STOPPED);
			return FALSE;    
		}

		LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STARTED);
		SetStatus(SERVICE_RUNNING);

		DebugMsg(_T("Leaving BasicNTService::Initialize()"));
		return TRUE;
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// main function to do the real work of the service

	// This function performs the main work of the service. 
	// When this function returns the service has stopped.
	void BasicNTService::Run()
	{
		DebugMsg(_T("Entering BasicNTService::Run()"));

		while (m_bIsRunning) {
			DebugMsg(_T("Sleeping..."));
			Sleep(5000);
		}

		// nothing more to do
		DebugMsg(_T("Leaving BasicNTService::Run()"));
	}

	//////////////////////////////////////////////////////////////////////////////////////
	// Control request handlers

	// static member function (callback) to handle commands from the
	// service control manager
	void BasicNTService::Handler(DWORD dwOpcode)
	{
		// Get a pointer to the object
		BasicNTService* pService = m_pThis;

		pService->DebugMsg(_T("BasicNTService::Handler(%lu)"), dwOpcode);
		switch (dwOpcode) {
	case SERVICE_CONTROL_STOP: // 1
		pService->SetStatus(SERVICE_STOP_PENDING);
		pService->OnStop();
		pService->m_bIsRunning = FALSE;
		pService->LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED);
		break;

	case SERVICE_CONTROL_PAUSE: // 2
		pService->OnPause();
		break;

	case SERVICE_CONTROL_CONTINUE: // 3
		pService->OnContinue();
		break;

	case SERVICE_CONTROL_INTERROGATE: // 4
		pService->OnInterrogate();
		break;

	case SERVICE_CONTROL_SHUTDOWN: // 5
		pService->OnShutdown();
		break;

	default:
		if (dwOpcode >= SERVICE_CONTROL_USER) {
			if (!pService->OnUserControl(dwOpcode)) {
				pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
			}
		} else {
			pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
		}
		break;
		}

		// Report current status
		pService->DebugMsg(_T("Updating status (%lu, %lu)"),
			pService->m_hServiceStatus,
			pService->m_Status.dwCurrentState);
		::SetServiceStatus(pService->m_hServiceStatus, &pService->m_Status);
	}

	// Called when the service is first initialized
	BOOL BasicNTService::OnInit()
	{
		DebugMsg(_T("BasicNTService::OnInit()"));
		return TRUE;
	}

	// Called when the service control manager wants to stop the service
	void BasicNTService::OnStop()
	{
		DebugMsg(_T("BasicNTService::OnStop()"));
	}

	// called when the service is interrogated
	void BasicNTService::OnInterrogate()
	{
		DebugMsg(_T("BasicNTService::OnInterrogate()"));
	}

	// called when the service is paused
	void BasicNTService::OnPause()
	{
		DebugMsg(_T("BasicNTService::OnPause()"));
	}

	// called when the service is continued
	void BasicNTService::OnContinue()
	{
		DebugMsg(_T("BasicNTService::OnContinue()"));
	}

	// called when the service is shut down
	void BasicNTService::OnShutdown()
	{
		DebugMsg(_T("BasicNTService::OnShutdown()"));
	}

	// called when the service gets a user control message
	BOOL BasicNTService::OnUserControl(DWORD dwOpcode)
	{
		DebugMsg(_T("BasicNTService::OnUserControl(%8.8lXH)"), dwOpcode);
		return FALSE; // say not handled
	}


	////////////////////////////////////////////////////////////////////////////////////////////
	// Debugging support

	void BasicNTService::DebugMsg(const TCHAR* pszFormat, ...)
	{
		TCHAR buf[1024];
		_stprintf(buf, _T("xxm:[%s](%lu): "), m_szServiceName, GetCurrentThreadId());
		va_list arglist;
		va_start(arglist, pszFormat);
		_vstprintf(&buf[_tcslen(buf)], pszFormat, arglist);
		va_end(arglist);
		_tcscat(buf, _T("\n"));
		OutputDebugString(buf);
	}

}

